今天,我們要為 Gym Pro 設計登入頁面和主要的導航結構,並討論如何使用 React Query 來優化我們的 API 請求。
首先,我們將設計一個簡潔且具吸引力的登入頁面,並使用 shadcn/ui
的組件來快速建立 UI。
在 src/pages/Login.tsx
中:
import React from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "@/components/ui/card"
interface LoginForm {
email: string;
password: string;
}
const Login: React.FC = () => {
const { register, handleSubmit } = useForm<LoginForm>();
const navigate = useNavigate();
const onSubmit = (data: LoginForm) => {
console.log(data);
// 這裡應該處理登入邏輯
// 成功後導航到儀表板
navigate('/dashboard');
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<Card className="w-[350px]">
<CardHeader>
<CardTitle>歡迎來到 Gym Pro</CardTitle>
<CardDescription>請登入以繼續</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="space-y-4">
<Input {...register('email')} type="email" placeholder="Email" required />
<Input {...register('password')} type="password" placeholder="密碼" required />
</div>
<Button className="w-full mt-4" type="submit">登入</Button>
</form>
</CardContent>
<CardFooter>
<p className="text-sm text-gray-500">還沒有帳號?請聯繫管理員</p>
</CardFooter>
</Card>
</div>
);
};
export default Login;
接下來,我們來更新我們的主要布局,包括側邊導航欄和頂部的用戶頭像。
在 src/layouts/MainLayout.tsx
中:
import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
const navItems = [
{ path: '/dashboard', label: '儀表板' },
{ path: '/members', label: '會員管理' },
{ path: '/classes', label: '課程管理' },
{ path: '/reports', label: '報表' },
];
interface MainLayoutProps {
children: React.ReactNode;
}
const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
const location = useLocation();
return (
<div className="flex h-screen bg-gray-100">
<nav className="w-64 bg-white shadow-md">
<div className="p-4">
<h1 className="text-2xl font-bold text-gray-800">Gym Pro</h1>
</div>
<ul className="space-y-2 p-4">
{navItems.map((item) => (
<li key={item.path}>
<Link to={item.path}>
<Button
variant="ghost"
className={cn(
"w-full justify-start",
location.pathname === item.path && "bg-gray-100"
)}
>
{item.label}
</Button>
</Link>
</li>
))}
</ul>
</nav>
<main className="flex-1 overflow-y-auto">
<header className="bg-white shadow-sm">
<div className="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8 flex justify-between items-center">
<h2 className="text-xl font-semibold text-gray-800">
{navItems.find(item => item.path === location.pathname)?.label}
</h2>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="使用者" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</div>
</header>
<div className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
{children}
</div>
</main>
</div>
);
};
export default MainLayout;
React Query 是一個強大的狀態管理工具,特別適合處理伺服器數據。我們將利用 React Query 來優化 API 請求的管理。
首先,安裝 React Query:
npm install @tanstack/react-query
然後,在 src/main.tsx
中設置 React Query:
import React from 'react'
import ReactDOM from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import App from './App.tsx'
import './index.css'
const queryClient = new QueryClient()
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>,
)
接下來,建立一個 src/hooks/useUser.ts
來獲取用戶資料:
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
interface User {
id: number;
name: string;
email: string;
avatar: string;
}
const fetchUser = async (): Promise<User> => {
const { data } = await axios.get<User>('/api/user');
return data;
};
export const useUser = () => {
return useQuery(['user'], fetchUser);
};
現在,我們可以在任何組件中使用這個 hook 來獲取用戶資料。例如,在 MainLayout
中:
import { useUser } from '@/hooks/useUser';
// 在組件內部
const { data: user, isLoading } = useUser();
// 在 Avatar 組件中
<Avatar>
{isLoading ? (
<AvatarFallback>載入中...</AvatarFallback>
) : (
<>
<AvatarImage src={user?.avatar} alt={user?.name} />
<AvatarFallback>{user?.name?.[0]}</AvatarFallback>
</>
)}
</Avatar>
今天我們完成了: